<script setup lang="ts">
import { ref, nextTick, shallowRef, watch, onMounted, onUnmounted, provide } from 'vue'
import modules from './modules'
import Drawer from './panel/Drawer.vue'
import PieMenu from './panel/PieMenu.vue'
import ComponentViews from './panel/ComponentViews.vue'
import ViewsTree from './panel/ViewsTree.vue'
import classes from './PageEditor.module.css'
import NPage from './components/NPage/NPage'
import Controls from './Controls/Controls'
import HoverBorder from './Controls/HoverBorder'
import enterDrop from './Controls/enterDrop'
import './ItemNode/animation.css'
import { ElButton, ElTag, ElTabs, ElTabPane, ElScrollbar } from 'element-plus'
import { debounce, downloadFile, uploadFile, UndoHistory, fromJson, toJson, toVue } from './utils'
import { shortcutKey, getTip } from './shortcut'
import { editing } from './store/store.js'

const emit = defineEmits(['exit', 'change'])

const editorRef = ref()
const viewNodes = shallowRef([])

editing.value = true

viewNodes.value = Object.values(modules)
  .filter((c: any) => c.preview)
  .map((n: any) => new n())

// 菜单
const menusData = ref([
  {
    title: '退出',
    handleClick: () => {
      emit('exit')
    }
  },
  {
    title: '全屏',
    handleClick: () => {
      const el = editorRef.value
      if (!el) return
      if (document.fullscreenElement == el) document.exitFullscreen()
      else el.requestFullscreen()
    }
  },
  {
    title: '编辑',
    children: [
      {
        title: `撤销 ${getTip(shortcutKey.undo)}`,
        handleClick: () => {
          undo()
        }
      },
      {
        title: `恢复 ${getTip(shortcutKey.redo)}`,
        handleClick: () => {
          redo()
        }
      }
    ]
  },
  {
    title: '文件',
    children: [
      {
        title: '导出',
        children: [
          {
            title: '导出当前编辑页面vue',
            handleClick: () => {
              if (!page.value) return

              page.value.resetStyle()
              toVue(page.value).then((content) => {
                downloadFile(content, 'Page.zip')
              })
            }
          },
          {
            title: '导出当前编辑页面json',
            handleClick: () => {
              if (!page.value) return

              page.value.resetStyle()
              const pageStr = toJson(page.value)
              downloadFile(pageStr, 'page.json')
            }
          },
          {
            title: `导出所有页面json ${getTip(shortcutKey.save)}`,
            handleClick: () => {
              save()
            }
          }
        ]
      },
      {
        title: '导入json',
        handleClick: () => {
          uploadFile((file) => {
            file.text().then((res) => {
              const page = fromJson(res)

              if (Array.isArray(page)) page.forEach((p) => addPage(p))
              else addPage(page)
            })
          })
        }
      }
    ]
  }
])

// 控制
const controlsRef = ref()
// 页面
const allPages = ref([]) // 用shallowRef响应渲染有问题
const page = ref() // 当前编辑页面

provide('allPages', allPages)

let pageComponent = null
const selectedNode = shallowRef()
const undoHistories = new WeakMap() // 页面操作历史记录
// 设置当前编辑的页面
const setPage = (pageNode) => {
  page.value = pageNode
  selectedNode.value = null

  if (!pageNode) return

  pageComponent = pageNode.component

  const index = allPages.value.findIndex((p) => p.uuid == pageNode.uuid)
  if (index !== -1) allPages.value.splice(index, 1, page.value)
}
// 新建页面
const addPage = (pageNode) => {
  undoHistories[pageNode.uuid] = new UndoHistory()
  allPages.value.push(pageNode)
  setPage(pageNode)
}
// 删除页面
const removePage = (pageNode) => {
  const index = allPages.value.findIndex((p) => p.uuid == pageNode.uuid)

  if (index !== -1) allPages.value.splice(index, 1)

  if (pageNode.uuid == page.value.uuid) {
    const beforePage = allPages.value[index - 1]
    const afterPage = allPages.value[index]
    if (afterPage) setPage(afterPage)
    else setPage(beforePage)
  }
}
/**
 *
 * @param {string|object} json 设置编辑的内容；v-model不适用
 */
const setData = (json?: string | object) => {
  allPages.value = []

  if (!json) return addPage(new NPage())

  const page = typeof json === 'string' ? fromJson(json) : json

  if (Array.isArray(page)) page.forEach((p) => addPage(p))
  else addPage(page)
}
/**
 *
 * @param {boolean} all true导出所有页面，false导出当前编辑页面
 * @return {string}
 */
const getData: (all: boolean) => string = (all) => {
  return all ? toJson(allPages.value) : toJson(page.value)
}
// 监听修改
watch(
  [page, allPages],
  debounce(() => {
    emit('change', toJson(page.value), toJson(allPages.value))
    // 存操作历史
    if (!isFromHistrory && page.value) {
      const undoHistory = undoHistories[page.value.uuid]
      undoHistory?.push(toJson(page.value))
    } else isFromHistrory = false
  }, 500),
  { deep: true, flush: 'post' }
)
// 下载保存所有页面数据
const save = () => {
  allPages.value.forEach((p) => p.resetStyle())
  const allPagesJson = toJson(allPages.value)
  downloadFile(allPagesJson, 'allPages.json')
}
// 操作历史
let isFromHistrory = false // 避免操作重复进历史
// 撤销操作
const undo = () => {
  if (!page.value) return

  const undoHistory = undoHistories[page.value.uuid]

  undoHistory?.undo((state) => {
    setPage(fromJson(state))

    isFromHistrory = true
  })
}
// 恢复操作
const redo = () => {
  if (!page.value) return

  const undoHistory = undoHistories[page.value.uuid]

  undoHistory?.redo((state) => {
    setPage(fromJson(state))

    isFromHistrory = true
  })
}
// 选择节点
const selectNode = (node) => {
  selectedNode.value = node

  nextTick(() => {
    controlsRef.value.control(node)
  })
}
// 点选
const onSelectNode = (e) => {
  selectNode(e.targetNode)
}
// 悬停
const hoveredNode = shallowRef()
const hoverBorderRef = ref()
const onHoverNode = (e) => {
  hoveredNode.value = e.targetNode
  hoverBorderRef.value.hover(e.targetNode)
}
const onMouseleave = (e) => {
  hoveredNode.value = null
  hoverBorderRef.value.clearHover()
}
// 拖拽
const dragNode = shallowRef()
const dropNode = shallowRef()
const onDragNode = (e) => {
  dragNode.value = e.targetNode
}
// 防抖拖放
const wait = 200
const onDragenterNode = debounce((e) => {
  if (!dragNode.value) return

  dropNode.value = e.targetNode
  enterDrop(dragNode.value, dropNode.value)
  selectNode(dragNode.value)
}, wait)
const onDragend = (e) => {
  setTimeout(() => (dragNode.value = dropNode.value = null), wait)
}
// 移除节点
const onRemoveNode = () => {
  selectedNode.value = null
}
// 快捷键
const shortcutKeyEvent = (e: KeyboardEvent) => {
  const { undo: undoKey, redo: redoKey, save: saveKey } = shortcutKey

  if (
    e.code == undoKey.code &&
    e.shiftKey == undoKey.shiftKey &&
    e.ctrlKey == undoKey.ctrlKey &&
    e.altKey == undoKey.altKey
  ) {
    // 事件阻断写在判断内，不污染页面事件
    e.preventDefault()
    e.stopPropagation()
    undo()
  }

  if (
    e.code == redoKey.code &&
    e.shiftKey == redoKey.shiftKey &&
    e.ctrlKey == redoKey.ctrlKey &&
    e.altKey == redoKey.altKey
  ) {
    e.preventDefault()
    e.stopPropagation()
    redo()
  }

  if (
    e.code == saveKey.code &&
    e.shiftKey == saveKey.shiftKey &&
    e.ctrlKey == saveKey.ctrlKey &&
    e.altKey == saveKey.altKey
  ) {
    e.preventDefault()
    e.stopPropagation()
    save()
  }
}

onMounted(() => {
  document.addEventListener('keydown', shortcutKeyEvent)

  editorRef.value.classList.add('editing', editing.value)
})

onUnmounted(() => {
  editing.value = false
  document.removeEventListener('keydown', shortcutKeyEvent)
})

defineExpose({
  setData,
  getData
})
</script>

<template>
  <div ref="editorRef" :class="classes['editor']" @dragend="onDragend" @drag-node="onDragNode">
    <PieMenu :data="menusData"></PieMenu>
    <!-- 左侧组件面板 -->
    <Drawer width="350px" direction="ltr">
      <template #header>
        <span>组件视图</span>
      </template>
      <template #default>
        <ElTabs type="border-card">
          <ElTabPane label="组件">
            <ComponentViews :view-nodes="viewNodes"></ComponentViews>
          </ElTabPane>
          <ElTabPane label="视图结构">
            <ElScrollbar>
              <ViewsTree
                v-if="page"
                :page="page"
                :hovered-node="hoveredNode"
                :selected-node="selectedNode"
                :controls="controlsRef"
                @hover-node="onHoverNode"
                @select-node="onSelectNode"
              ></ViewsTree>
            </ElScrollbar>
          </ElTabPane>
        </ElTabs>
      </template>
    </Drawer>
    <!-- 渲染 -->
    <div
      v-if="page"
      :class="classes['render-container']"
      @mouseleave="onMouseleave"
      @hover-node="onHoverNode"
      @select-node="onSelectNode"
      @dragenter-node="onDragenterNode"
    >
      <pageComponent></pageComponent>
    </div>
    <!-- 右侧参数面板 -->
    <Drawer width="350px">
      <template #header>
        <span>参数</span>
      </template>
      <template v-if="selectedNode" #default>
        <component :is="selectedNode.params"></component>
      </template>
    </Drawer>
    <!-- 标签页 -->
    <Drawer width="30px" direction="ttb">
      <ElTag
        v-for="item of allPages"
        :key="item.uuid"
        :color="item.uuid == page?.uuid ? 'var(--color-5)' : 'var(--color-1)'"
        effect="dark"
        style="cursor: pointer"
        size="small"
        closable
        @click="setPage(item)"
        @close="removePage(item)"
      >
        {{ item.props.name }}
      </ElTag>
      <ElButton size="small" circle icon="Plus" @click="addPage(new NPage())"> </ElButton>
    </Drawer>
    <!-- 控制 -->
    <Controls
      v-show="selectedNode"
      ref="controlsRef"
      :page="page"
      @remove-node="onRemoveNode"
    ></Controls>
    <HoverBorder v-show="hoveredNode" ref="hoverBorderRef"></HoverBorder>
  </div>
</template>
